/**
 * 
 */
package com.primestone.cim.executemeterreadschedules;

import static com.primestone.cim.util.StringUtil._NULL_BOOLEAN_;
import static com.primestone.cim.util.StringUtil._NULL_STRING_;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.xml.ws.Holder;

import ch.iec.tc57._2011.meterreadschedules.CustomerAccount;
import ch.iec.tc57._2011.meterreadschedules.CustomerAgreement;
import ch.iec.tc57._2011.meterreadschedules.EndDeviceAsset;
import ch.iec.tc57._2011.meterreadschedules.EndDeviceGroup;
import ch.iec.tc57._2011.meterreadschedules.FaultMessage;
import ch.iec.tc57._2011.meterreadschedules.MeterReadSchedules;
import ch.iec.tc57._2011.meterreadschedules.ReadingType;
import ch.iec.tc57._2011.meterreadschedules.TimeSchedule;
import ch.iec.tc57._2011.meterreadschedules.TimeSchedule.TimePoints;
import ch.iec.tc57._2011.meterreadschedulesmessage.MeterReadSchedulesFaultMessageType;
import ch.iec.tc57._2011.meterreadschedulesmessage.MeterReadSchedulesPayloadType;
import ch.iec.tc57._2011.schema.message.HeaderType;
import ch.iec.tc57._2011.schema.message.MessageProperty;
import ch.iec.tc57._2011.schema.message.ReplyType;
import ch.iec.tc57._2011.schema.message.UserType;

import com.primestone.cim.util.CollectionUtil;
import com.primestone.cim.util.DateUtil;
import com.primestone.cim.util.NumberUtil;
import com.primestone.cim.util.RandomUtil;
import com.primestone.cim.util.StringUtil;
import com.primestone.cim.util.enums.CIMNoun;
import com.primestone.cim.util.enums.CIMVerb;
import com.primestone.cim.util.enums.MessageEnum;
import com.primestone.cim.util.enums.ReadingTypeEnum.TimeAttribute;
import com.primestone.cim.util.enums.ResultEnum;

/**
 * @author cesar.mateus
 *
 */
public class Completer {

	public static MeterReadSchedules fillMeterReadSchedules(List<ScheduleOperationResult> queryResult) {

		MeterReadSchedules meterReadSchedules = new MeterReadSchedules();

		//List<ScheduleOperationResult> savedMeterReadSchedules = filterSavedMeterReadSchedules(queryResult);
		
		
		//fillCustomerAccountList(meterReadSchedules.getCustomerAccount());
		//fillCustomerAgreementList(meterReadSchedules.getCustomerAgreement());
		fillEndDeviceAssetList(meterReadSchedules.getEndDeviceAsset(), queryResult);
		//fillEndDeviceGroupList(meterReadSchedules.getEndDeviceGroup());
		//fillReadingTypeList(meterReadSchedules.getReadingType());
		fillTimeScheduleList(meterReadSchedules.getTimeSchedule(), queryResult);

		return meterReadSchedules;
	}

	public static void fillCustomerAccountList(List<CustomerAccount> customerAccountList) {
		// TODO iterar sobre una lista de CustomerAccounts
		customerAccountList.add(fillCustomerAccount());
	}

	public static CustomerAccount fillCustomerAccount(/*aca falta ver que param se necesita*/) {
		CustomerAccount customerAccount = new CustomerAccount();
		customerAccount.setMRID(_NULL_STRING_); // required
		return customerAccount;
	}

	public static void fillCustomerAgreementList(List<CustomerAgreement> customerAgreementList) {
		// TODO iterar sobre una lista de CustomerAgreements
		customerAgreementList.add(fillCustomerAgreement());
	}

	public static CustomerAgreement fillCustomerAgreement(/*aca falta ver que param se necesita*/) {
		CustomerAgreement customerAgreement = new CustomerAgreement();
		customerAgreement.setMRID(_NULL_STRING_); // required
		return customerAgreement;
	}

	public static void fillEndDeviceAssetList(List<EndDeviceAsset> endDeviceAssetList, List<ScheduleOperationResult> queryResult) {
		// TODO iterar sobre una lista de EndDeviceAsset
		//for (ScheduleOperationResult scheduleOperationResult : queryResult) {
			endDeviceAssetList.add(fillEndDeviceAsset());	
		//}
	}

	public static EndDeviceAsset fillEndDeviceAsset(/*aca falta ver que param se necesita*/) {
		EndDeviceAsset endDeviceAsset = new EndDeviceAsset();
		endDeviceAsset.setMRID(_NULL_STRING_); // required
		return endDeviceAsset;
	}

	public static void fillEndDeviceGroupList(List<EndDeviceGroup> endDeviceGroupList) {
		// TODO iterar sobre una lista de EndDeviceGroup
		endDeviceGroupList.add(fillEndDeviceGroup());
	}

	public static EndDeviceGroup fillEndDeviceGroup(/*aca falta ver que param se necesita*/) {
		EndDeviceGroup endDeviceGroup = new EndDeviceGroup();
		endDeviceGroup.setGroupAddress(NumberUtil._NULL_BIGINTEGER_); // required
		return endDeviceGroup;
	}

	public static void fillReadingTypeList(List<ReadingType> readingTypeList) {
		// TODO iterar sobre una lista de ReadingType
		readingTypeList.add(fillReadingType());
	}

	public static ReadingType fillReadingType(/*aca falta ver que param se necesita*/) {
		ReadingType readingType = new ReadingType();
		readingType.setMRID(_NULL_STRING_); // required
		readingType.setName(_NULL_STRING_);
		return readingType;
	}

	public static void fillTimeScheduleList(List<TimeSchedule> timeScheduleList, List<ScheduleOperationResult> queryResult) {
		// TODO iterar sobre una lista de TimeSchedule
		timeScheduleList.add(fillTimeSchedule());
	}

	public static TimeSchedule fillTimeSchedule(/*aca falta ver que param se necesita*/) {
		TimeSchedule timeSchedule = new TimeSchedule();

		timeSchedule.setDisabled(_NULL_BOOLEAN_);
		timeSchedule.setOffset(NumberUtil._NULL_FLOAT_);
		timeSchedule.setRecurrencePattern(_NULL_STRING_);
		timeSchedule.setRecurrencePeriod(NumberUtil._NULL_FLOAT_);
		timeSchedule.setScheduleInterval(fillScheduleInterval()); // required

		fillTimePointsList(timeSchedule.getTimePoints());

		return timeSchedule;
	}

	public static void fillTimePointsList(List<TimePoints> timePointsList) {
		// TODO iterar sobre una lista de TimePoints
		timePointsList.add(fillTimePoints());
	}

	public static TimePoints fillTimePoints(/*aca falta ver que param se necesita*/) {
		TimePoints timePoints = new TimePoints();

		timePoints.setAbsolutTime(DateUtil._NULL_DATE_);
		timePoints.setRelativeTimeinterval(NumberUtil._NULL_FLOAT_);
		timePoints.setSequenceNumber(NumberUtil._NULL_BIGINTEGER_);
		timePoints.setWindow(fillWindow());

		return timePoints;
	}

	public static TimeSchedule.ScheduleInterval fillScheduleInterval(/*aca falta ver que param se necesita*/) {
		TimeSchedule.ScheduleInterval scheduleInterval = new TimeSchedule.ScheduleInterval();
		scheduleInterval.setEnd(DateUtil._NULL_DATE_); // required
		scheduleInterval.setStart(DateUtil._NULL_DATE_); // required
		return scheduleInterval;
	}

	public static TimeSchedule.TimePoints.Window fillWindow(/*aca falta ver que param se necesita*/) {
		TimeSchedule.TimePoints.Window window = new TimeSchedule.TimePoints.Window();
		window.setEnd(DateUtil._NULL_DATE_); // required
		window.setStart(DateUtil._NULL_DATE_); // required
		return window;
	}

	public static String extractTicket(Holder<HeaderType> header) {
		if (header.value != null) {
			List<MessageProperty> properties = header.value.getProperty();
			for (MessageProperty messageProperty : properties) {
				if(messageProperty.getName().equals(StringUtil.TICKET_FIELD_NAME)) {
					return messageProperty.getValue();
				}
			}
		}
		return null;
	}

	public static void putTicket(Holder<HeaderType> header, String ticket) {
		if (header.value != null) {			
			MessageProperty messageProperty = new MessageProperty();
			messageProperty.setName(StringUtil.TICKET_FIELD_NAME);
			messageProperty.setValue(ticket);
			header.value.getProperty().add(messageProperty);			
		}
	}

	public static FaultMessage composeFault(String message) {
		MeterReadSchedulesFaultMessageType meterReadSchedulesFaultMessageType = new MeterReadSchedulesFaultMessageType();
		ReplyType replyType = new ReplyType();
		replyType.setResult(ResultEnum.FAILED.getName());
		meterReadSchedulesFaultMessageType.setReply(replyType);			
		return new FaultMessage(message, meterReadSchedulesFaultMessageType);
	}

	/**
	 * TODO ajustar esta validacion de header de ser necesario.
	 * @param header
	 * @return
	 */
	public static FaultMessage validateHeader(Holder<HeaderType> header, CIMVerb verb) {
		FaultMessage faultMessage = null;
		if (header == null || header.value == null || header.value.getUser() == null
				|| header.value.getUser().getUserID() == null) {
			faultMessage = Completer.composeFault(MessageEnum.INCOMPLETE_USER_INFO.getDescription());			
		} else if (header.value.getVerb() == null) {			
			faultMessage = Completer.composeFault(MessageEnum.INCOMPLETE_VERB_INFO.getDescription());
		} else if (!header.value.getVerb().equals(verb.getVerb())) {
			faultMessage = Completer.composeFault(MessageEnum.INCORRECT_VERB_INFO.getDescription());
		} else if (header.value.getNoun() == null) {
			faultMessage = Completer.composeFault(MessageEnum.INCOMPLETE_NOUN_INFO.getDescription());
		} else if (!header.value.getNoun().equals(CIMNoun.METER_READ_SCHEDULE_NOUN.getNoun())) {
			faultMessage = Completer.composeFault(MessageEnum.INCORRECT_NOUN_INFO.getDescription());
		}
		return faultMessage;
	}

	/**
	 * 
	 * @param payload
	 * @return
	 */
	public static FaultMessage validatePayload(Holder<MeterReadSchedulesPayloadType> payload) {
		FaultMessage faultMessage = null;
		if (payload == null || payload.value == null) {
			faultMessage = Completer.composeFault(MessageEnum.INCOMPLETE_PAYLOAD_INFO.getDescription());			
		} else {
			MeterReadSchedules meterReadSchedules = payload.value.getMeterReadSchedules();
			if (meterReadSchedules == null) {
				faultMessage = Completer.composeFault(MessageEnum.INCOMPLETE_PAYLOAD_MRS_INFO.getDescription());
			} else {
				List<EndDeviceAsset> endDeviceAssets = meterReadSchedules.getEndDeviceAsset();
				if (!CollectionUtil.safe(endDeviceAssets)) {
					faultMessage = Completer.composeFault(MessageEnum.INCOMPLETE_PAYLOAD_EDA_INFO.getDescription());				
				} else {
					for (EndDeviceAsset endDeviceAsset : endDeviceAssets) {
						if (!StringUtil.safe(endDeviceAsset.getMRID())) {
							return Completer.composeFault(MessageEnum.INCOMPLETE_PAYLOAD_EDA_MRID_INFO.getDescription());
						}
					}
				}
				List<TimeSchedule> timeSchedules = meterReadSchedules.getTimeSchedule();
				if (!CollectionUtil.safe(timeSchedules)) {
					faultMessage = Completer.composeFault(MessageEnum.INCOMPLETE_PAYLOAD_TS_INFO.getDescription());				
				} else {
					for (TimeSchedule timeSchedule : timeSchedules) {
						if (timeSchedule.getScheduleInterval() == null) {
							return Completer.composeFault(MessageEnum.INCOMPLETE_PAYLOAD_TS_SI_INFO.getDescription());
						} else {
							if (timeSchedule.getScheduleInterval().getStart() == null) {
								return Completer.composeFault(MessageEnum.INCOMPLETE_PAYLOAD_TS_SI_START_INFO.getDescription());
							} else if (timeSchedule.getRecurrencePattern() == null) {
								return Completer.composeFault(MessageEnum.INCOMPLETE_PAYLOAD_TS_RP_INFO.getDescription());
							}
						}
					}
				}	
			}
		}
		
		return faultMessage;
	}

	public static HeaderType constructExecMeterReadSchedulesHeader(CIMVerb verb) {
		HeaderType headerType = new HeaderType();
		headerType.setVerb(verb.getVerb());
		headerType.setNoun(CIMNoun.METER_READ_SCHEDULE_NOUN.getNoun());
		headerType.setRevision(StringUtil.DEFAULT_REVISION);
		headerType.setContext(StringUtil.TESTING_CONTEXT);

		headerType.setTimestamp(DateUtil.getXMLGregorianCalendarFromGregorianCalendar());

		headerType.setSource(StringUtil.SOURCE);
		headerType.setMessageID(RandomUtil.nextString());

		UserType userType = new UserType();
		userType.setUserID("Test");
		userType.setOrganization(StringUtil.ORGANIZATION);

		headerType.setUser(userType);

		return headerType;
	}
	
	public static List<Integer> getDataFromRecurrencePattern(String recurrencePattern) {
		List<Integer> periodAndFrequency = new ArrayList<Integer>(); 
		TimeAttribute timeAttribute = TimeAttribute.getByIndex(recurrencePattern);
		if (timeAttribute != null) {
			switch (timeAttribute) {			
			case _1: {
				periodAndFrequency.add(DateUtil.FREQ_MINUTE);
				periodAndFrequency.add(10);
				break;
			}
			case _2: {
				periodAndFrequency.add(DateUtil.FREQ_MINUTE);
				periodAndFrequency.add(15);
				break;
			}
			case _3: {
				periodAndFrequency.add(DateUtil.FREQ_MINUTE);
				periodAndFrequency.add(1);
				break;
			}
			case _4: {
				periodAndFrequency.add(DateUtil.FREQ_HOUR);
				periodAndFrequency.add(24);
				break;
			}
			case _5: {
				periodAndFrequency.add(DateUtil.FREQ_MINUTE);
				periodAndFrequency.add(30);
				break;
			}
			case _6: {
				periodAndFrequency.add(DateUtil.FREQ_MINUTE);
				periodAndFrequency.add(5);
				break;
			}
			case _7: {
				periodAndFrequency.add(DateUtil.FREQ_MINUTE);
				periodAndFrequency.add(60);
				break;
			}
			case _10: {
				periodAndFrequency.add(DateUtil.FREQ_MINUTE);
				periodAndFrequency.add(2);
				break;
			}
			case _11: {
				periodAndFrequency.add(DateUtil.FREQ_DAY);
				periodAndFrequency.add(1);
				break;
			}
			case _13: {
				periodAndFrequency.add(DateUtil.FREQ_MONTH);
				periodAndFrequency.add(1);
				break;
			}
			case _14: {
				periodAndFrequency.add(DateUtil.FREQ_MINUTE);
				periodAndFrequency.add(3);
				break;
			}
			case _24: {
				periodAndFrequency.add(DateUtil.FREQ_WEEK);
				periodAndFrequency.add(1);
				break;
			}
			default:
				periodAndFrequency.add(DateUtil.FREQ_NONE);
				periodAndFrequency.add(0);
			}	
		} else {
			periodAndFrequency.add(DateUtil.FREQ_NONE);
			periodAndFrequency.add(0);
		}

		return periodAndFrequency;
	}
	
	public static List<ScheduleOperationResult> filterSavedMeterReadSchedules( List<ScheduleOperationResult> allSchedules) {
		List<ScheduleOperationResult> savedSchedules = new ArrayList<ScheduleOperationResult>();
		for (ScheduleOperationResult scheduleOperationResult : allSchedules) {
			if ( scheduleOperationResult.getIsSaved() ) {
				savedSchedules.add(scheduleOperationResult);
			}
		}
		return savedSchedules;
	}
	
	public static ArrayList<ScheduleOperationResult> filterFailedMeterReadSchedules( List<ScheduleOperationResult> allSchedules) {
		ArrayList<ScheduleOperationResult> failedSchedules = new ArrayList<ScheduleOperationResult>();
		for (ScheduleOperationResult scheduleOperationResult : allSchedules) {
			if ( !scheduleOperationResult.getIsSaved() ) {
				failedSchedules.add(scheduleOperationResult);
			}
		}
		return failedSchedules;
	}
	
	private static void populateMap(Map<String, List<ScheduleOperationResult>> map, String mridDevice, ScheduleOperationResult schedule) {
		List<ScheduleOperationResult> schedules;
		Set<String> keySet = map.keySet();
		if (keySet.contains(mridDevice)) {
			schedules = map.get(mridDevice);
			schedules.add(schedule);
		} else {
			schedules = new ArrayList<ScheduleOperationResult>();
			schedules.add(schedule);
		}
		map.put(mridDevice, schedules);
	}
	
	public static void classifyByDevice(Map<String, ArrayList<ScheduleOperationResult>> map, List<ScheduleOperationResult> schedulesResult) {
		ArrayList<ScheduleOperationResult> schedules;
		Set<String> keySet = map.keySet();
		for (ScheduleOperationResult schedule : schedulesResult) {
			if (keySet.contains(schedule.getIdDevice())) {
				schedules = map.get(schedule.getIdDevice());
				schedules.add(schedule);
			} else {
				schedules = new ArrayList<ScheduleOperationResult>();
				schedules.add(schedule);
			}
			map.put(schedule.getIdDevice(), schedules);		
		}	
	}

}
